Blog

  • ffmpeg – 한줄의 명령어로 폴더 내의 여러개의 동영상을 하나로 합치기

    ffmpeg를 이용해서 여러개로 쪼개어진 촬영한 영상 파일이나 영화파일들을 트랜스코딩 없이 하나로 합칠 수 있는 한 줄의 명령어이다.

    macos의 경우 FreeBSD의 find를 사용하기 때문에 -printf 옵션과 같은 다양한 옵션들을 지원하지 않아서 사용할 수 없다.

    따라서 먼저 brew를 이용해 GNU findutils를 설치해야 한다.

    brew install findutils

    터미널에서 원하는 폴더로 이동한 뒤 아래의 명령을 입력하면 폴더 안에 있는 여러개의 동영상들을 하나의 파일로 합칠 수 있으며 결과물의 파일명은 폴더명으로 생성된다.

    printf ${PWD##*/}.mp4 | xargs -0 -I {} ffmpeg -safe 0 -f concat -i <(gfind . -type f -name '*.mp4' -printf "file '$PWD/%p'\n" | sort) -c copy {}

    macos에서 samba share로 공유된 네트워크 폴더에서 작업할 경우 dotfile로 인해 명령을 실행할 때 오류가 날 수 있으니 아래와 같은 명령으로 dotfile을 제거 후 명령을 실행 할 수 있고 원본파일명의 패턴을 이용해 합친 후 원본파일을 제거할 수 있다.

    rm ./._* ;  printf ${PWD##*/}.mp4 | xargs -0 -I {} ffmpeg -safe 0 -f concat -i <(gfind . -type f -name '*.mp4' -printf "file '$PWD/%p'\n" | sort) -c copy {} && rm ./*hevc.mp4
  • HandBrakeCLI – 폴더 내의 동영상 일괄 변환하기

    handbrakecli를 이용해서 폴더 내의 동영상을 미리 정의된 preset을 사용해서 일괄 변환하는 shell script이다. 여러 동영상 파일들을 한번에 h265 hevc 형식으로 일괄 변환하고 싶을 때 퍽 유용하다.

    #!/bin/bash
    #
    # This script requires HandBrakeCLI. On macOS, at least, you need to download
    # and install it separately from Handbrake.
    #
    # See: https://handbrake.fr/downloads2.php
    
    if [ -z "$1" ] ; then
        TRANSCODEDIR="."
    else
        TRANSCODEDIR="$1"
    fi
        # Change the preset if you like (see options: "HandBrakeCLI --preset-list")
        # If you want to transcode ALL movie files, remove the -name option.
        find "$TRANSCODEDIR"/* -type f -name "*.mp4" -exec bash -c 'HandBrakeCLI -i "$1" -o "${1%\.*}".hevc.mp4 --preset-import-file ~/2hevc.json -Z "2hevc"' __ {} \;

    https://www.jeffgeerling.com/blog/2022/batch-transcode-folder-videos-handbrakes-cli

  • 하데스 2(Hades II)

    드디어 기다리던 하데스의 후속편이 기술 시험 단계로 일반에게 공개되었다.

    아래의 링크에서 참여할 수 있고 테스터로 선정되어야 플레이가 가능하다.

    https://www.supergiantgames.com/blog/hades2-tech-test-sign-up

    크로노스에게 죽음을… – 멜리노에

  • Avidemux – HDTV 녹화 파일의 편집

    Plex Media Server에서 Live TV 프로그램을 녹화하면 HDTV 방송용 표준형식인 MPEG2-TS 형식으로 저장된다.

    40분 정도의 프로그램 한 편을 저장하면 대략 3기가가 넘는 크기로 제법 크기가 크다.

    그런데 막상 녹화를 하면 프로그램 중간 광고라든지 예약녹화일 경우 정확한 방영 일정에 맞출 수 없기 때문에(우리나라 방송사들은 프로그램 방영 시간을 정확하게 지키지 않는다.) 광고나 다른 프로그램의 일부가 함께 녹화된다.

    이럴 때 녹화된 파일에서 원치 않는 부분을 잘라내야하는 등의 간단한 편집을 해야하는 상황이 발생하는데 Avidemux는 mpeg2ts 파일을 손실없이 편집할 수있는 유용한 프로그램이다.

    무료로 이용할 수 있는 오픈소스 프로그램이며 macOS는 물론, Linux와 Windows도 지원한다.

    UI는 다소 투박하지만 왠만한 상용 프로그램 보다 나은 기능들을 제공한다.

  • Nostalgia

    매일 밤 9시가 되면 어김없이 둘째 아들 꼬꼬마 녀석의 취침 준비를 위해서 준비한 영상이 구글 네스트 허브에서 재생된다.

    처음에는 재밌으라고 그렇게 해뒀는데 나도 한 번씩 영상을 볼 때마다 그때의 향수를 느낀다.

    https://youtu.be/zuwgn8xBb5w
  • HandBrake – vtb_h265 인코더를 이용한 동영상 인코딩

    여러가지 이유로 소장용 영화나 TV프로그램들을 변환해야하는 상황이 있을 수 있다. 요사이 Plex Media Server(PMS)에서 녹화한 TV프로그램들의 파일 크기가 너무 커서 SSD에 담아두기 부담스러운 상황이 발생했다.

    녹화한 TV 파일들이 HD 방송표준 형식인 mpeg2ts 형식의 동영상들이라서 공간을 제법 차지하고 있어서 h.265 hevc로 파일 크기를 줄이고 싶있었다.

    변환에 사용할 소프트웨어로는 상용 프로그램인 Final Cut Pro – Compressor를 선택 할 수 있고 무료로 사용할 수 있는 HandBrake를 선택할 수 있는데 나는 가난하므로 HandBrake를 선택했다.

    HandBrake(이하 HB)는 대중적인 오픈소스 프로그램으로 내부적으로는 FFmpeg를 사용한다. 하지만 ffmpeg가 그러하듯 제공되는 너무나 다양한 옵션으로 처음 접하는 사용자들은 눈이 휘둥그레질 수 있다.

    HandBrake

    나는 Apple M1 맥북 에어를 사용 중인데 인코딩할 때 gpu 하드웨어 가속을 쓰려면 어떻게 해야 하는지가 궁금했고 원본과 비슷한 화질을 유지하면서 결과물의 파일 크기를 줄이기 위해 앞서 말한대로 hevc 코덱을 사용하기를 원했다.
    요구되는 사항들은 다음과 같이 세 가지이다.

    1. hevc 코덱과 GPU 하드웨어 가속(Hardware Acceleration)을 사용해서 인코딩하기
    2. 내게 맞는 최적의 변환 옵션 찾기 – 스윗스팟(sweet spot)을 찾아라!
    3. HandBrakeCLI를 이용해서 폴더 단위로 동영상을 손쉽게 변환 할 수 있도록 배치 스크립트 작성하기

    먼저, 내가 사용 중인 M1 맥북의 경우 하드웨어 가속을 사용하려면 HB에서는 Apple에서 제공하는 VideoToolBox(VTB) Framework을 이용하므로 HB에서 제공하는 옵션을 확인하자. 제공하는 문서는 아래의 링크에서 자세히 볼 수 있다.

    https://handbrake.fr/docs/en/latest/technical/video-videotoolbox.html

    HB에서 제공하는 VTB용 비디오 인코더는 세 가지이며

    • H.264 (VideoToolbox) – vtb_h264
    • H.265 (VideoToolbox) – vtb_h265
    • H.265 10bit (VideoToolbox) – vt_h265_10bit

    사전에 미리 제공되는 기본 프리셋은 두 가지이다.(Preset> Hardware)

    • H.265 Apple VideoToolbox 2160P 4K
    • H.265 Apple VideoToolbox 1080P

    물론 미리 제공되는 프리셋으로 변환할 수도 있지만 자신이 원하는 스윗스팟을 찾으려면 옵션들을 변경하면서 다양한 결과물을 직접 확인해야한다. 그러기위해서는 샘플영상을 만들자. (한 시간이 넘어가는 영화파일로 스윗스팟을 찾으려면 날샌다.)

    mp4 파일의 경우, 샘플영상의 자르기는 ffmpeg를 사용하며 아래 링크에서 자세한 내용들을 볼 수 있다.

    https://www.arj.no/2018/05/18/trimvideo

    ffmpeg를 설치하고 커멘드 창을 열고 아래와 같이 커멘드를 입력해 원하는 샘플 파일을 준비한다.

    ffmpeg -i input-file.mp4 -ss 00:02:30 -t 00:03:00 -c copy output-file.mp4
    ffmpeg -sseof -00:10:00 -i input.mp4 -c copy output6.mp4
    • ffmpeg
    • -i input-file.mp4 – 입력으로 사용할 파일명
    • -ss 00:02:30 – 시작 시점 (HH:MM:SS)
    • -t 00:03:00 – 기간 (3분)
    • -c copy – 재인코딩 없이 오디오와 비디오 스트림을 복사
    • output-file.mp4 – 출력으로 사용될 파일명
    • -sseof – eof(End of File)

    mkv 파일의 경우, MKVToolNix를 설치하고 아래와 같이 입력한다.

    mkvmerge -o output-file.mkv input-file.mkv --split parts:00:02:30-00:05:30
    • mkvmerge
    • -o output-file.mkv – 출력으로 사용할 파일명
    • input-file.mkv – 입력되는 파일명
    • –split parts: – 자르기를 원하는 장면의 시작되는 시점과 종료되는 시점의 타임스탬프

    두 번째로는 샘플영상이 마련되었으니 최적의 변환 옵션을 찾아야하는데 하드웨어 가속이 가능한 vtb_265 인코더의 경우에는 Constant Quality(이하 cq) 값이 중요하다.

    기본 제공되는 프리셋의 경우 FHD는 60, 4K는 55로 설정되어 있다.

    HB에서 샘플파일을 열고 Preset메뉴에서 ‘Hardware’ 폴더 내에 있는 ‘H.265 Apple VideoToolbox 1080P’ 프리셋을 선택해서 로드해 cq값을 변경해가며 시험해보자.

    왼쪽부터 원본, cq60, cq55 순이다. (원본을 확인하려면 클릭)

    나의 경우 FHD의 경우 cq 52~55가 가장 좋았다. 원하는 cq값으로 변경한 뒤 ‘Save as New Preset’을 선택해 따로 프리셋을 저장한다.

    만일 인코딩 작업 중에 M1 맥북에어가 너무 높은 온도로 인해 스로틀링이 발생하는 것을 방지하려면 HB의 ‘Video> Additional Options’에 “threads=2″를 입력하는 것과 같이 작업에 사용될 threads를 제한해 두자.

    https://www.jeffgeerling.com/blog/2022/limiting-handbrake-threads-prevent-throttling-on-m2-macbook-air

    마지막으로 원하는 cq값을 찾아서 프리셋을 완성했으므로 HandBrakeCLI를 사용해 배치작업을 실행해 보자.

    HandBrakeCLI

    HB에서는 gui 프로그램과 cli 프로그램이 서로 프리셋 공유가 되지 않는다.
    따라서 gui 앱에서 완성한 프리셋을 json파일로 내보내기를 한뒤, 다시 cli에서 그 파일을 가져오기해서 사용해야 한다.

    gui 프로그램에서 ‘Preset> Export’ 메뉴를 선택해 원하는 위치에 json 파일을 저장하고 아래와 같이 cli에서 사용하면 된다.

    HandBrakeCLI -v --preset-import-file 'exported-preset-file.json' -Z 'preset-name' -i input-file.mp4 -o output-file.mp4

    여기서 ‘preset-name‘은 ‘exported-preset-file.json‘ 내에 기입된 preset의 이름이다. (json파일을 열어보면 확인할 수 있다.)

    사용자 preset은 HandBrakeCLI에서는 별도로 저장되지 않기 때문에 변환할 때마다 써넣어줘야 한다.

    성공적으로 변환을 마쳤다면 이제 스크립트를 작성해 배치작업을 해보자.

    #!/bin/bash
    
    if [ -z "$1" ] ; then
        TRANSCODEDIR="."
    else
        TRANSCODEDIR="$1"
    fi
        find "$TRANSCODEDIR"/* -type f -name "*.mp4" -exec bash -c 'HandBrakeCLI -i "$1" -o "${1%\.*}"-out.mp4 --preset-import-file exported-preset-file.json -Z "preset-name"' __ {} \;

    https://www.jeffgeerling.com/blog/2022/batch-transcode-folder-videos-handbrakes-cli

    $>sh encoding.sh 'path-and-folder-name'

    자, 이제 원하는 TV 시리즈를 배치 작업으로 간단하게 변환할 수 있다.
    변환 작업에 사용될 thread를 제한해 두었으므로(threads=2) 맥북에서 동시에 다른 작업을 하기에도 유용하다.

    HandBrakeCLI에서 인코딩할 때 사용된 preset – AV.json

    {
      "PresetList" : [
        {
          "AlignAVStart" : false,
          "AudioCopyMask" : [
            "copy:aac"
          ],
          "AudioEncoderFallback" : "none",
          "AudioLanguageList" : [
            "any"
          ],
          "AudioList" : [
            {
              "AudioBitrate" : 160,
              "AudioCompressionLevel" : -1,
              "AudioDitherMethod" : "auto",
              "AudioEncoder" : "copy:aac",
              "AudioMixdown" : "stereo",
              "AudioNormalizeMixLevel" : false,
              "AudioSamplerate" : "auto",
              "AudioTrackDRCSlider" : 0,
              "AudioTrackGainSlider" : 0,
              "AudioTrackQuality" : -1,
              "AudioTrackQualityEnable" : false
            }
          ],
          "AudioSecondaryEncoderMode" : true,
          "AudioTrackSelectionBehavior" : "first",
          "ChapterMarkers" : true,
          "ChildrenArray" : [
    
          ],
          "Default" : false,
          "FileFormat" : "av_mp4",
          "Folder" : false,
          "FolderOpen" : false,
          "InlineParameterSets" : false,
          "MetadataPassthrough" : true,
          "Mp4iPodCompatible" : false,
          "Optimize" : false,
          "PictureAllowUpscaling" : false,
          "PictureAutoCrop" : true,
          "PictureBottomCrop" : 0,
          "PictureChromaSmoothCustom" : "",
          "PictureChromaSmoothPreset" : "off",
          "PictureChromaSmoothTune" : "none",
          "PictureColorspaceCustom" : "",
          "PictureColorspacePreset" : "off",
          "PictureCombDetectCustom" : "",
          "PictureCombDetectPreset" : "off",
          "PictureCropMode" : 2,
          "PictureDARWidth" : 1920,
          "PictureDeblockCustom" : "strength=strong:thresh=20:blocksize=8",
          "PictureDeblockPreset" : "off",
          "PictureDeblockTune" : "medium",
          "PictureDeinterlaceCustom" : "",
          "PictureDeinterlaceFilter" : "off",
          "PictureDeinterlacePreset" : "default",
          "PictureDenoiseCustom" : "",
          "PictureDenoiseFilter" : "off",
          "PictureDenoisePreset" : "light",
          "PictureDenoiseTune" : "none",
          "PictureDetelecine" : "off",
          "PictureDetelecineCustom" : "",
          "PictureForceHeight" : 0,
          "PictureForceWidth" : 0,
          "PictureHeight" : 0,
          "PictureItuPAR" : false,
          "PictureKeepRatio" : true,
          "PictureLeftCrop" : 0,
          "PictureModulus" : 2,
          "PicturePadBottom" : 0,
          "PicturePadColor" : "black",
          "PicturePadLeft" : 0,
          "PicturePadMode" : "none",
          "PicturePadRight" : 0,
          "PicturePadTop" : 0,
          "PicturePAR" : "off",
          "PicturePARHeight" : 1,
          "PicturePARWidth" : 1,
          "PictureRightCrop" : 0,
          "PictureRotate" : "angle=0:hflip=0",
          "PictureSharpenCustom" : "",
          "PictureSharpenFilter" : "off",
          "PictureSharpenPreset" : "medium",
          "PictureSharpenTune" : "none",
          "PictureTopCrop" : 0,
          "PictureUseMaximumSize" : true,
          "PictureWidth" : 0,
          "PresetDescription" : "",
          "PresetDisabled" : false,
          "PresetName" : "AV",
          "SubtitleAddCC" : false,
          "SubtitleAddForeignAudioSearch" : false,
          "SubtitleAddForeignAudioSubtitle" : false,
          "SubtitleBurnBDSub" : true,
          "SubtitleBurnBehavior" : "none",
          "SubtitleBurnDVDSub" : true,
          "SubtitleLanguageList" : [
            "any"
          ],
          "SubtitleTrackSelectionBehavior" : "none",
          "Type" : 1,
          "UsesPictureFilters" : true,
          "VideoAvgBitrate" : 175000,
          "VideoColorMatrixCodeOverride" : 0,
          "VideoEncoder" : "vt_h265",
          "VideoFramerate" : "auto",
          "VideoFramerateMode" : "vfr",
          "VideoGrayScale" : false,
          "VideoHWDecode" : 0,
          "VideoLevel" : "auto",
          "VideoMultiPass" : false,
          "VideoOptionExtra" : "threads=2",
          "VideoPreset" : "quality",
          "VideoProfile" : "auto",
          "VideoQSVDecode" : false,
          "VideoQualitySlider" : 55,
          "VideoQualityType" : 2,
          "VideoScaler" : "swscale",
          "VideoTune" : "",
          "VideoTurboMultiPass" : true,
          "x264Option" : "",
          "x264UseAdvancedOptions" : false
        }
      ],
      "VersionMajor" : 53,
      "VersionMicro" : 0,
      "VersionMinor" : 0
    }
  • 난 무해하지 않아!

    이런 메시지를 받았다.

    무해-하다1 (無害하다) 「형용사」 해로움이 없다

    – 국립국어원 발췌

    무해하지 않다 = 해로움이 없지 않다 = 해롭다 = 유해하다

    이건 바로 이중부정의 말장난이다.

    그래서 여러가지 생각들이 들었다.

    비록 전자담배가 “무해”하지는 않지만 “무해”하다는 이미지가 내 머릿속에 오인되길 바랐을까? 아래처럼?

    담배는 건강에 해롭다. (다들 알고 계시겠지만… )
    하지만 전자담배는 신기술로 만들어진 담배로 기존의 담배와는 다르다. (너희가 잘 모를 거 같으니, 우리가 알려줄게.)
    전자담배는 해롭지 않을 수도 있다.

    아니면, *관공서에서 하라고 하니깐 하긴 하는데 우리 제품에 좋지 않은 이미지를 전달하고 싶진 않아. 뭐 이런 느낌이었을까?

    그것도 아니라면, 내 국어 실력을 검증이라도 하려는 것이었을까?

    뭔가 계속 숨은 의도에 대해 생각하게 된다.

    그냥 “본 제품은 만 19세 이상 성인을 위한 제품이므로 유해합니다.”라고 썼더라면 뭐, 당연한 얘기니까 그냥 쓱 넘겼을 것을….

    * 현재 담배사업법과 국민건강증진법, 청소년보호법, 여성가족부 고시 제2017-46호에 의하면 광고에는 ‘청소년 유해 표시’를 하도록 되어 있다.

  • Hades – 게임 저장 오류

    Supergiant Games하데스는(Hades) 아주 잘 만들어진 게임으로 나에게는 오랫동안 즐겨도 물리지 않는 그런 종류의 게임이다.

    처음에는 닌텐도 스위치로 하다가 다음은 Xbox 게임패스, 지금은 스팀덱으로 즐기고 있다. 스팀 포함 각각, 총 세 번 구매한 셈. 인디게임이라 판매가격도 원래 저렴하기도 하지만 세 번을 사도 아깝지 않은 게임이다.

    내가 생각하는 이게임의 유일한 불만은 Dolby Vision과 Atmos를 지원하지 않는다는 것.(팔월드도 지원하는 것을…) Dolby Vision을 지원했더라면 아마도 스팀판 게임에서 HDR을 지원했을 거고 그러면 OLED 스팀덱에서 더욱 미려하게 플레이할 수 있었을 거다.

    그런데 최근에 이 게임의 치명적인 문제가 하나 생겼는데 게임의 진행상황이 올바르게 저장되지 않는 문제가 발생했다. 스팀판에서만 발생하는 것은 아니고 Xbox용 버전에도 발생했던 문제이다.

    1층 타르타로스에서 부터 게임을 시작해 4층인 스틱스 신전을 지나 최종보스인 하데스를 알현하고 스틱스에 의해 다시 붙잡히는 일련의 과정인 한 판(one run)을 완료하고 게임을 마치면 다시 게임을 시작할 때에는 그 내용이 저장되지 않는다.

    https://youtu.be/6eyOl2P6j30

    이 문제를 Supergiant games에 이메일로 문의했으나 속편을 만드느라 바쁜 건지 아니면 내놓으라는 속편은 안내놓고 최근 공개한 Netflix용 Hades 때문인지 회답 없이 감감 무소식이다.

    아래는 검토를 위해 첨부한 세이브 파일이다.

    hades-saved_games-on-google-drive

    업데이트 2024.4.23
    이 문제를 수정한 세이브 파일을 Supergiant Games에서 보내왔고 문제는 해결되었다.

  • Vaultwarden – admin page 활성화

    현재 password manager로 bitwarden의 self-host 버전인 vaultwarden을 docker에 올려서 사용 중이다.

    vaultwarden에서 특정 vault를 가족들과 공유하기 위해서는 공유 초대를 해야하는데 공유초대를 하면 서버에서 자동으로 발송되는 초대 메일의 응답주소가 localhost로 기입되어서 vault의 공유 초대 수락이 완료되지 않는 문제가 발생하였다.

    이 문제를 해결하기 위해서는 server의 url 변수localhost에서 현재 사용 중인 실제 url 주소로 변경하기 위해서는 admin page를 활성화해서 해당 변수값을 변경해주어야 한다.

    1. ADMIN_TOKEN 생성
      아래의 명령을 입력해서 사용하려는 비밀번호를 두 번 입력하고 출력된 hash코드를 복사한다.
    docker exec -it vwcontainer-name /vualtwarden hash
    1. docker-compose.yml에 입력
      이때에는 변수 보간(variable interpolation)을 방지하기 위해 $ 문자는 $$로 변경하여 기입해야 한다.
            environment:
              - ADMIN_TOKEN=$$argon2id$$v=19$$m=65540,t=3,p=4$$bghsFgSXjamK58rXNE+YJjsTTMJJnSsTpnvPNRzb2wQ$$B/6E4Oi8ttJaqDo02L3fvlyll9SlJ1cWAxStHYjFyEA
    1. ‘https://vault-server-url/admin‘에 접속해서 hash코드를 생성할 때 설정했던 비밀번호를 입력하여 접속한다.
    2. admin page 비활성화
      두 번째 단계에서 docker-compose.yml에서 기입했던 ADMIN_TOKEN을 # comment 처리하고 ‘https://vault-server-url/admin’ 페이지에서 설정되어 있는 토큰값을 지운 후 vaultwarden을 재시작 한다.

    https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#using-vaultwarden-hash

  • Hello world!

    워드프레스를 다시 설치하고 블로깅을 다시 시작한다!